
#include "StreamAudioWidget.h"

const int PREFERRED_BUFFER_SIZE = 4096;		// Buffer size. Increase to get more stable sound output, decrease to
											// get lower latency. On Linux you'll need a low latency patch in the
											// kernel to get good results at buffersizes around 128 samples.

// Fixed synthesis parameters
const double FUND = 440.;					// Fundamental frequency
const int NO_PARTIALS = 8;					// Number of partials

// Various fixed constants
const double PI = acos(-1.0);
const double TWO_PI = 2.*acos(-1.0);

//#define FRAMES_PER_BUFFER (AUDIO_SAMPLE_RATE)
#define SAMPLE_SILENCE (0.0f)

/* This routine will be called by the PortAudio engine
** when audio is needed.
** It may be called at interrupt level on some machines, so
** don't do anything that could mess up the system, like
** calling malloc() or free().
*/
//int paCallback(const void *inputBuffer, void *outputBuffer,
//					unsigned long framesPerBuffer,
//					const PaStreamCallbackTimeInfo* info,
//					PaStreamCallbackFlags, void *userData)
//{
//	StreamAudioWidget *wdg = (StreamAudioWidget*)userData;
//	const SAMPLE *in = (const SAMPLE*)inputBuffer;
//	SAMPLE *out = (SAMPLE*)outputBuffer;
//	float leftInput, rightInput;
//	unsigned int i;
//
//	if (wdg->isMuted())
//		memset(out, 0, sizeof(SAMPLE) * framesPerBuffer * AUDIO_NUM_CHANNELS);
//	else
//	{
//		if (!wdg->isPlayback())
//			memset(out, 0, sizeof(SAMPLE) * framesPerBuffer * AUDIO_NUM_CHANNELS);
//		else
//		{
//			/* Read input buffer, process data, and fill output buffer. */
//			for( i=0; i<framesPerBuffer; i++ )
//			{
//				leftInput = *in++;		/* Get interleaved samples from input buffer. */
//				rightInput = *in++;
//				*out++ = leftInput;
//				*out++ = rightInput;
//			}
//		}
//
//		// process
//		wdg->processAudio(out, framesPerBuffer, AUDIO_NUM_CHANNELS, info);
//	}
//
//	return paContinue;
//}

int paCallbackNonInterleave(const void *inputBuffer, void *outputBuffer,
					unsigned long framesPerBuffer,
					const PaStreamCallbackTimeInfo* info,
					PaStreamCallbackFlags, void *userData)
{
	StreamAudioWidget *wdg = (StreamAudioWidget*)userData;
	const TYPE_SAMPLE_IN **in = (const TYPE_SAMPLE_IN**)inputBuffer;
	TYPE_SAMPLE_OUT **out = (TYPE_SAMPLE_OUT**)outputBuffer;
	unsigned int i;
	int channels = wdg->audioChannels();

	if (wdg->isMuted())
	{
		for (int i=0; i<channels; i++)
			memset(out[i], 0, sizeof(TYPE_SAMPLE_IN) * framesPerBuffer);
	}
	else
	{
		if (!wdg->isPlayback())
		{
			for (int i=0; i<channels; i++)
				memset(out[i], 0, sizeof(TYPE_SAMPLE_IN) * framesPerBuffer);
		}
		else
		{
			for (int i=0; i<framesPerBuffer; i++)
			{
				for (int j=0; j<channels; j++)
				{
					out[j][i] = in[j][i];
				}
			}
		}

		// process data
		wdg->processAudio(in, framesPerBuffer);
	}

	return paContinue;
}

QStringList StreamAudioWidget::getListOfInputDevices()
{
	PaError err;
	PaDeviceIndex ndev;
	const PaDeviceInfo *info;
	int i;
	QStringList lsDev;

	err = Pa_Initialize();
	if( err == paNoError){
		PaStreamParameters outputP;
		outputP.device = Pa_GetDefaultOutputDevice(); // default output
		outputP.channelCount = m_nChannels;
		outputP.sampleFormat = paFloat32;
		outputP.suggestedLatency = Pa_GetDeviceInfo(outputP.device)->defaultLowOutputLatency;
		outputP.hostApiSpecificStreamInfo = NULL;
		ndev = Pa_GetDeviceCount();
		for(i=0; i<ndev; i++){
			info = Pa_GetDeviceInfo((PaDeviceIndex) i);
			if (info->maxInputChannels > 0)
			{
				// Try to open stream with current device
				PaStreamParameters inputP;	
				inputP.channelCount = m_nChannels;
				inputP.sampleFormat = paFloat32;
				inputP.device = (PaDeviceIndex)i;
				inputP.suggestedLatency = Pa_GetDeviceInfo( inputP.device )->defaultLowInputLatency;
				inputP.hostApiSpecificStreamInfo = NULL;
				err = Pa_OpenStream(
					&stream,
					&inputP,
					&outputP,
					m_nSampleRate,
					(m_nSampleRate),
					paClipOff,
					NULL,
					NULL );
				// if failed, don't add to input device list
				if(err != paNoError) {
					continue;
				}
				else
				{
					lsDev << info->name;
					Pa_CloseStream(stream);
					stream = NULL;
				}
			}
		}
		Pa_Terminate();
	}
	
	return lsDev;
}

int StreamAudioWidget::getDeviceIndexByName(QString sDevName)
{
	PaError err;
	PaDeviceIndex ndev;
	const PaDeviceInfo *info;
	int i, nRet=-1;

	err = Pa_Initialize();
	if( err == paNoError){
		ndev = Pa_GetDeviceCount();
		for(i=0; i<ndev; i++){
			info = Pa_GetDeviceInfo((PaDeviceIndex) i);
			if (info->maxInputChannels > 0 && sDevName.compare(QString(info->name))==0)
			{
				nRet = i;
				break;
			}
		}
		Pa_Terminate();
	}
	else {
		qFatal("Initialize failed.");
	}
	
	return nRet;
}

QString StreamAudioWidget::getDeviceNameByIndex(int nDevIdx)
{
	PaError err;
	PaDeviceIndex ndev;
	const PaDeviceInfo *info;
	QString sDevName = QString();

	err = Pa_Initialize();
	if( err == paNoError){
		ndev = Pa_GetDeviceCount();
		info = Pa_GetDeviceInfo((PaDeviceIndex) nDevIdx);
		if (info)
		{
			sDevName = QString(info->name);
		}
		Pa_Terminate();
	}
	else {
		qFatal("Initialize failed.");
	}
	
	return sDevName;
}

int StreamAudioWidget::getDefaultInputDevice()
{
	PaError err;	
	int nDefaultDevIdx = -1;

	err = Pa_Initialize();
	if( err == paNoError){
		nDefaultDevIdx = Pa_GetDefaultInputDevice();
		Pa_Terminate();
	}
	
	return nDefaultDevIdx;
}

int StreamAudioWidget::startCaptureOnDevice(int nDevIdx) {
	// Initialize PortAudio
	PaError err = Pa_Initialize();
	if( err != paNoError ) {
		qFatal("PortAudio: Initialization error");
		return 1;
	}

	if (nDevIdx == -1)
		nDevIdx = Pa_GetDefaultInputDevice();

	PaStreamParameters inputP;
	inputP.device = (PaDeviceIndex)nDevIdx;
	inputP.channelCount = m_nChannels;		//stereo input
	inputP.sampleFormat = paFloat32 | paNonInterleaved;//PA_SAMPLE_TYPE | paNonInterleaved;
	inputP.suggestedLatency = Pa_GetDeviceInfo( inputP.device )->defaultLowInputLatency;
	inputP.hostApiSpecificStreamInfo = NULL;

	PaStreamParameters outputP;
	outputP.device = Pa_GetDefaultOutputDevice(); // default output
	outputP.channelCount = m_nChannels; // stereo output
	outputP.sampleFormat = paFloat32 | paNonInterleaved;//PA_SAMPLE_TYPE | paNonInterleaved;
	outputP.suggestedLatency = Pa_GetDeviceInfo(outputP.device)->defaultLowOutputLatency;
	outputP.hostApiSpecificStreamInfo = NULL;

	// Initialize interface
	err = Pa_OpenStream(
		&stream,
		&inputP,
		&outputP,
		m_nSampleRate,
		(m_nSampleRate / 5),
		paClipOff,
		paCallbackNonInterleave,
		this );

	if(err != paNoError) {
		//qFatal("PortAudio: Open stream error: %s", Pa_GetErrorText(err));
		return 2;
	}


	// Start stream
	err = Pa_StartStream( stream );
	if( err != paNoError ) {
		//qFatal("PortAudio: Start stream error: %s", Pa_GetErrorText(err));
		return 3;
	}

	return 0;
}

int StreamAudioWidget::stopCaptureOnDevice() {
	PaError err;

	if(stream && Pa_IsStreamActive(stream)) {
		err = Pa_StopStream(stream);
		if(err != paNoError) {
			//qFatal("PortAudio: Stop stream error: %s", Pa_GetErrorText(err));
			return 1;
		}

		err = Pa_CloseStream( stream );
		if( err != paNoError ) {
			//qFatal("PortAudio: Close stream error: %s", Pa_GetErrorText(err));
			return 1;
		}
		stream = NULL;
	}

	Pa_Terminate();

	return 0;
}

StreamAudioWidget::StreamAudioWidget(const int channels, const int sampleRate) :
	stream(0), m_bMuted(false), m_bPlayBack(false),
	m_nChannels(channels), m_nSampleRate(sampleRate)
{
	// create interface
	createInterface();
	m_timerUpdate = new QTimer();
	m_timerUpdate->setInterval(200);

	// connect signals
	connect(m_chkMute, SIGNAL(stateChanged(int)), this, SLOT(onSetMute(int)));
	connect(m_chkPlayback, SIGNAL(stateChanged(int)), this, SLOT(onSetPlayback(int)));
	connect(m_cmbChannels, SIGNAL(currentIndexChanged(int)), this, SLOT(onChangeSource(int)));
	connect(m_timerUpdate, SIGNAL(timeout()), this, SLOT(onTimer()));

	m_timerUpdate->start();
}

StreamAudioWidget::~StreamAudioWidget(void)
{
}

void StreamAudioWidget::createInterface()
{
	m_lblAudio = new QLabel(tr(LABEL_SOURCE));
	m_cmbChannels = new QComboBox();
	m_cmbChannels->addItems(getListOfInputDevices());
	// find default input device index and set default combobox item
	int nDefaultItemIdx = m_cmbChannels->findText(getDeviceNameByIndex(getDefaultInputDevice()));
	m_cmbChannels->setCurrentIndex(nDefaultItemIdx);
	m_chkMute = new QCheckBox(tr(LABEL_MUTE));
	m_chkMute->setChecked(m_bMuted);
	m_chkPlayback = new QCheckBox(tr(LABEL_PLAYBACK));
	m_chkPlayback->setChecked(m_bPlayBack);
	m_prgSoundIndicator = new QProgressBar();
	m_prgSoundIndicator->setOrientation(Qt::Horizontal);
	m_prgSoundIndicator->setFixedSize(50, 12);
	m_prgSoundIndicator->setRange(0, 100);
	m_prgSoundIndicator->setTextVisible(false);
	m_prgSoundIndicator->setInvertedAppearance(true);

	QHBoxLayout *layout = new QHBoxLayout();
	setLayout(layout);
	layout->addWidget(m_lblAudio);
	layout->addWidget(m_cmbChannels, 1);
	layout->addWidget(m_prgSoundIndicator);
	layout->addWidget(m_chkMute);
	layout->addWidget(m_chkPlayback);

	setTitle(tr(TITLE_SECTION_AUDIO));
	setStyleSheet(
		"QProgressBar:horizontal {background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #11EE11, stop: 1 red); border: 1px solid gray; border-radius: 3px; padding: 0px; text-align: right; margin-right: 0px;}"
		"QProgressBar::chunk:horizontal { background: white; margin-right: 0px; width: 1px;}"
	);
}

void StreamAudioWidget::setMute(bool bMuted)
{
	m_chkMute->setChecked(bMuted);
}

void StreamAudioWidget::onSetMute(int state)
{
	if (state == Qt::Checked)
	{
		m_bMuted = true;
		m_chkPlayback->setEnabled(false);
	}
	else
	{
		m_bMuted = false;
		m_chkPlayback->setEnabled(true);
	}
}

void StreamAudioWidget::setPlayback(bool bMuted)
{
	m_chkPlayback->setChecked(bMuted);
}

void StreamAudioWidget::onSetPlayback(int state)
{
	if (state == Qt::Checked)
		m_bPlayBack = true;
	else
		m_bPlayBack = false;
}


void StreamAudioWidget::onChangeSource(int idx)
{
	stopCaptureOnDevice();
	startCaptureOnDevice(getDeviceIndexByName(m_cmbChannels->itemText(idx)));
}

void StreamAudioWidget::processAudio(const TYPE_SAMPLE_IN **data, const int size)
{
	float energy = 0.0f;
	for (int i=0; i<m_nChannels; i++)
	{
		for (int j=0; j<size; j++)
			energy = energy + data[i][j] * data[i][j];
	}
	energy = ::sqrt(energy);
	energy = ::log(energy) * 20;
	m_nEnergy = (int)(energy);

	emit onCaptureAudio(data, size, m_nChannels, m_nSampleRate);
}

void StreamAudioWidget::reset(const int audioChannels, const int audioSample)
{
	stopCaptureOnDevice();
	m_nChannels = audioChannels;
	m_nSampleRate = audioSample;
	startCaptureOnDevice(getDeviceIndexByName(m_cmbChannels->currentText()));
}

void StreamAudioWidget::onTimer()
{
	int curVal = 100 - m_prgSoundIndicator->value();
	if (curVal - m_nEnergy <= 10)
		m_prgSoundIndicator->setValue(100 - m_nEnergy);
	else
		m_prgSoundIndicator->setValue(100 - curVal + 10);
	m_prgSoundIndicator->setToolTip(QString::number(m_nEnergy) + " %");
}
